Skip to content

UI: Replace window.confirm with custom confirmation modal - #12

Closed
tejaavula076 wants to merge 4 commits into
geturbackend:mainfrom
tejaavula076:tejaswi/9-ui-confirmation-modal
Closed

UI: Replace window.confirm with custom confirmation modal#12
tejaavula076 wants to merge 4 commits into
geturbackend:mainfrom
tejaavula076:tejaswi/9-ui-confirmation-modal

Conversation

@tejaavula076

@tejaavula076 tejaavula076 commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

🚀 Pull Request Description

Fixes #9

🛠️ Type of Change

🧪 Testing & Validation

Backend Verification: (I have not yet touched these components)

  • I have run npm test in the backend/ directory and all tests passed.
  • I have verified the API endpoints using Postman/Thunder Client.
  • New unit tests have been added (if applicable).

Frontend Verification:

  • I have run npm run lint in the frontend/ directory. - (Yes)
  • Verified the UI changes on different screen sizes (Responsive). - (yes)
  • Checked for any console errors in the browser dev tools. - (Yes, I have checked the console no issues)

📸 Screenshots / Recordings (Optional)

image image

✅ Checklist

  • My code follows the code style of this project. - Yes
  • I have performed a self-review of my code. - Yes
  • I have commented my code, particularly in hard-to-understand areas. - Yes
  • My changes generate no new warnings or errors. - Yes (There is no issue if you pull)
  • I have updated the documentation (README/Docs) accordingly. - (Not Yet)

Built with ❤️ for urBackend.

Summary by CodeRabbit

  • New Features

    • Modal confirmation dialogs for destructive actions
    • Project rename capability
    • Add-document modal with dynamic, schema-driven inputs
    • JSON view toggle for records
  • Improvements

    • Redesigned Database page: sidebar, table/JSON views, loaders, empty-state UX
    • Project settings: modal-based deletion and rename flow
    • Improved loading states with skeletons
  • Chores

    • Tailwind CSS integrated and modal-related styles added
    • Minor HTML/markup formatting cleanup

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

@tejaavula076 is attempting to deploy a commit to the Yash Pouranik's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

A reusable Tailwind-styled ConfirmationModal component was added and integrated into Database.jsx and ProjectSettings.jsx to replace native confirm dialogs. Tailwind was integrated into the frontend (deps + Vite plugin), small HTML formatting changes were made, and two CSS utility classes were added for modal spacing and buttons.

Changes

Cohort / File(s) Summary
HTML & Build Config
frontend/index.html, frontend/package.json, frontend/vite.config.js
Normalized doctype/HTML formatting, adjusted font link markup, added tailwindcss and @tailwindcss/vite to dependencies and enabled the Tailwind Vite plugin.
Global Styles
frontend/src/index.css
Imported Tailwind and added .confirmationModal and .confirmationModalBtn utility classes for modal spacing and button sizing.
New Component
frontend/src/pages/ConfirmationModal.jsx
Added default-exported React ConfirmationModal: overlay, alert icon, dynamic title/message, Cancel/Delete actions, overlay-dismiss and stopPropagation behavior.
Database Page
frontend/src/pages/Database.jsx
Reworked delete flow to use ConfirmationModal (showModal, selectedId), added add-document modal/form rendering, TableView/JsonView/SkeletonLoader UI pieces, and updated state/URL handling.
Project Settings
frontend/src/pages/ProjectSettings.jsx
Rewrote to load project data, added rename flow, and replaced inline confirm with ConfirmationModal for project deletion; integrated modal-driven UX.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as Database / ProjectSettings
    participant Modal as ConfirmationModal
    participant API as Backend API

    User->>Page: Click "Delete" (record or project)
    Page->>Page: set showModal=true, store targetId
    Page->>Modal: render(open=true, title, message, onConfirm, onCancel)
    Modal->>User: display overlay with Cancel/Delete

    alt User clicks Cancel
        User->>Modal: Click Cancel
        Modal->>Page: onCancel()
        Page->>Page: set showModal=false
    else User clicks Delete
        User->>Modal: Click Delete
        Modal->>Page: onConfirm()
        Page->>API: DELETE /resource/:id
        API-->>Page: 200 OK
        Page->>Page: remove item from state, set showModal=false
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A soft-tail modal hops into view,

A warning blink, a choice in red and gray,
No rude confirms to scare the crew,
Tailwind trims make the pathway true,
Click Delete or Cancel — then bounce away!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: replacing window.confirm with a custom confirmation modal, which is the core objective of this PR.
Linked Issues check ✅ Passed PR successfully implements all objectives from issue #9: creates a reusable ConfirmationModal component with warning icon, dynamic text, and styled buttons; integrates it into ProjectSettings.jsx and Database.jsx; uses React, Tailwind CSS, and Lucide React.
Out of Scope Changes check ✅ Passed Changes are tightly scoped to issue #9 requirements. Added CSS styling, HTML doctype/formatting, Tailwind integration, and new component files are all necessary to implement the custom modal solution.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In @frontend/index.html:
- Line 15: Remove the Tailwind CDN script tag from frontend/index.html (the
script src referencing @tailwindcss/browser) and rely on the Vite plugin
instead; update vite.config.js to import the plugin (referencing the imported
symbol tailwindcss) and add tailwindcss() to the plugins array alongside react()
so the plugin handles Tailwind build-time integration and optimization.

In @frontend/package.json:
- Around line 13-21: package.json currently lists the @tailwindcss/vite
dependency but vite.config.js does not use the plugin and index.html loads
Tailwind from CDN (@tailwindcss/browser@4), so remove the unused dependency by
deleting "@tailwindcss/vite" from package.json and run npm/yarn install;
alternatively, if you prefer build-time Tailwind, add the plugin to
vite.config.js (import and include @tailwindcss/vite in the plugins array) and
remove the CDN script from index.html—pick one approach and update package.json,
vite.config.js, and index.html accordingly to keep configuration and
dependencies consistent.

In @frontend/src/pages/ConfirmationModal.jsx:
- Around line 38-43: The confirm button in ConfirmationModal.jsx is hardcoded to
"Delete"; make it configurable by adding a confirmLabel prop (defaulting to
"Delete") and use that prop for the button text in the component (replace the
literal "Delete" with confirmLabel). Update all call sites (e.g.,
ProjectSettings.jsx) to pass an appropriate confirmLabel like "Delete", "Save",
"Confirm" or "Submit" depending on context so the modal is reusable across
scenarios.
- Line 13: The modal in ConfirmationModal.jsx currently forces a light theme via
the className string (e.g., "bg-white ... text-gray-900 text-gray-600"); update
the root container class in the ConfirmationModal component to use the app's
dark theme CSS variables instead (replace bg-white with a background using
var(--color-bg-main) or the surface variable, replace
text-gray-900/text-gray-600 with var(--color-text-main) and a muted text
variable such as var(--color-text-muted)), and ensure the modal buttons reuse
the app's existing dark-theme button classes so the modal matches the global
dark theme.
- Around line 8-15: The modal markup in ConfirmationModal.jsx lacks keyboard and
ARIA accessibility: add role="dialog", aria-modal="true" and
aria-labelledby/aria-describedby on the inner modal container, set initial focus
to the primary interactive element (e.g., the cancel/confirm button) when the
modal mounts, add an Escape key handler that calls onCancel (attach/remove on
mount/unmount), and implement a focus trap so Tab/Shift+Tab loop inside the
modal (use a small manual trap around the elements or integrate a library like
focus-trap-react/@headlessui and apply it to the element with className
"confirmationModal"). Ensure the click-away outer div still triggers onCancel
and the inner div stops propagation.

In @frontend/src/pages/Database.jsx:
- Around line 117-127: In handleAddDocument, avoid coercing empty/missing
optional fields which currently turn into NaN/false; before converting
formattedData[field.key] for Number or Boolean (iterating
activeCollection.model), only perform Number(...) or === "true" when
formattedData[field.key] is not null/undefined and not an empty string (e.g.,
check formattedData[field.key] != null && formattedData[field.key] !== "") so
absent values stay undefined/empty instead of becoming NaN/false.
- Around line 252-317: The ConfirmationModal is being rendered as a child of the
<table> inside the TableView component which produces invalid DOM; move the
modal out of the table element so it is a sibling (or placed outside the table
container) and keep its props and handlers intact (showModal, selectedId,
handleDelete, setShowModal, onConfirm/onCancel). Locate the TableView component
and remove the ConfirmationModal from inside the <table> JSX, then render it
immediately after the closing </table> (or at a higher-level sibling container)
so the modal overlays correctly without breaking table structure.
- Around line 47-70: The useEffect that defines fetchProject should not depend
on searchParams because updating URL params via setSearchParams causes an
unnecessary refetch loop; remove searchParams from the dependency array so
fetchProject only runs on changes to projectId or token, and keep the current
error handling in fetchProject; additionally, in the effect that runs when
activeCollection changes, guard the setSearchParams call by comparing the
current searchParams.get("collection") to activeCollection.name and only call
setSearchParams when they differ to avoid redundant URL updates (refer to
useEffect, fetchProject, searchParams, setSearchParams, and activeCollection).

In @frontend/src/pages/ProjectSettings.jsx:
- Around line 14-15: Update the inline comment above the state declaration for
showModal in ProjectSettings.jsx to correct the typo: change "model" to "modal"
so the comment reads something like "// used showModal to open the Confirmation
modal"; the unique symbol to locate is the const [showModal, setShowModal] =
useState(false) declaration.
- Around line 267-297: Remove the redundant confirmation modal and its state:
keep the typed-name safety check (deleteConfirm vs project?.name) and call
handleDeleteProject directly from the "Permanently Delete Project" button
onClick; remove showModal state, setShowModal usages, the ConfirmationModal
component usage and its props (open, title, message, onConfirm, onCancel), and
any related imports so the button executes handleDeleteProject immediately when
enabled while still being disabled unless deleteConfirm === project?.name.
🧹 Nitpick comments (12)
frontend/index.html (1)

9-14: Consider using modern font loading strategies.

The Google Fonts links are correctly preconnected, but for better performance, consider using font-display: swap in your CSS or switching to self-hosted fonts to eliminate the external dependency and improve Core Web Vitals.

frontend/src/index.css (1)

522-528: Replace custom CSS classes with Tailwind utilities.

Adding custom .confirmationModal and .confirmationModalBtn classes contradicts the Tailwind CSS approach you've adopted. These simple padding and width styles should use Tailwind utilities directly:

  • .confirmationModal (padding: 1rem) → use p-4 class
  • .confirmationModalBtn (padding: 5px; width: 5rem) → use px-1.5 py-1 w-20 classes

Mixing custom CSS with Tailwind utilities reduces consistency and makes the codebase harder to maintain.

♻️ Remove custom classes and use Tailwind utilities

Remove these CSS rules:

-}
-.confirmationModal{
-  padding: 1rem;
-}
-.confirmationModalBtn{
-      padding: 5px;
-    width: 5rem;
-
 }

Then update ConfirmationModal.jsx to use Tailwind classes directly:

-        className="bg-white w-[380px] rounded-2xl shadow-lg px-6 py-5 confirmationModal"
+        className="bg-white w-[380px] rounded-2xl shadow-lg px-6 py-5 p-4"
-            className="px-4 py-2 text-sm rounded-md border border-gray-300 text-gray-700 hover:bg-gray-100 confirmationModalBtn"
+            className="px-4 py-2 text-sm rounded-md border border-gray-300 text-gray-700 hover:bg-gray-100 w-20"

(Note: Verify exact spacing needs and adjust classes accordingly)

frontend/src/pages/ConfirmationModal.jsx (1)

1-50: Add prop validation for better developer experience.

Since you're using React 19 without TypeScript, consider adding runtime prop validation with prop-types to catch integration errors early.

📝 Add PropTypes

Install prop-types:

npm install prop-types

Then add to the component:

 import React, { useEffect, useRef } from "react";
 import { AlertTriangle } from "lucide-react";
+import PropTypes from "prop-types";

 function ConfirmationModal({ open, title, message, onConfirm, onCancel, confirmLabel, cancelLabel }) {
   // ...
 }

+ConfirmationModal.propTypes = {
+  open: PropTypes.bool.isRequired,
+  title: PropTypes.string.isRequired,
+  message: PropTypes.string.isRequired,
+  onConfirm: PropTypes.func.isRequired,
+  onCancel: PropTypes.func.isRequired,
+  confirmLabel: PropTypes.string,
+  cancelLabel: PropTypes.string
+};
+
 export default ConfirmationModal;
frontend/src/pages/ProjectSettings.jsx (6)

69-73: Remove commented-out code.

Leaving commented code in production degrades maintainability. If you need to preserve this for reference, document it in a commit message or comment explaining why the native confirm() was removed.

🧹 Remove dead code
   const handleDeleteProject = async () => {
     if (deleteConfirm !== project.name)
       return toast.error("Project name does not match");

-    // // if (
-    // //   !confirm("Final warning: This will delete the project and all its data.")
-    // // )
-    //   return;
-
     try {

26-42: Add error handling for network failures.

The fetchProject function logs errors via toast but doesn't handle specific failure scenarios. Consider:

  1. 401/403: Token expired or unauthorized → redirect to login
  2. 404: Project not found → redirect to dashboard with message
  3. Network error: Retry mechanism or offline indicator
🔧 Enhanced error handling
   useEffect(() => {
     const fetchProject = async () => {
       try {
         const res = await axios.get(`${API_URL}/api/projects/${projectId}`, {
           headers: { Authorization: `Bearer ${token}` },
         });
         setProject(res.data);
         setNewName(res.data.name);
-      } catch {
-        toast.error("Failed to load project");
+      } catch (err) {
+        if (err.response?.status === 401 || err.response?.status === 403) {
+          toast.error("Session expired. Please log in again.");
+          navigate("/login");
+        } else if (err.response?.status === 404) {
+          toast.error("Project not found");
+          navigate("/dashboard");
+        } else {
+          toast.error("Failed to load project");
+        }
       } finally {
         setLoading(false);
       }
     };
     fetchProject();
   }, [projectId, token, navigate]);

45-63: Validate project name format and length.

The current validation only checks for empty/whitespace names. Consider adding:

  • Minimum/maximum length constraints
  • Character restrictions (e.g., no special characters if your backend doesn't support them)
  • Trim whitespace before submission
✅ Add comprehensive validation
   const handleRename = async () => {
-    if (!newName.trim()) return toast.error("Project name cannot be empty");
+    const trimmedName = newName.trim();
+    
+    if (!trimmedName) {
+      return toast.error("Project name cannot be empty");
+    }
+    
+    if (trimmedName.length < 3) {
+      return toast.error("Project name must be at least 3 characters");
+    }
+    
+    if (trimmedName.length > 50) {
+      return toast.error("Project name must be less than 50 characters");
+    }

     setRenaming(true);
     try {
       await axios.patch(
         `${API_URL}/api/projects/${projectId}`,
-        { name: newName },
+        { name: trimmedName },
         { headers: { Authorization: `Bearer ${token}` } }
       );
       toast.success("Project renamed successfully!");
-      setProject((prev) => ({ ...prev, name: newName }));
+      setProject((prev) => ({ ...prev, name: trimmedName }));
+      setNewName(trimmedName); // Update state with trimmed version
     } catch {

304-467: Consider extracting DatabaseConfigForm to a separate file.

This component is 160+ lines and handles its own state and API logic. Keeping it in the same file as ProjectSettings reduces modularity and makes testing harder.

Recommendation: Extract to frontend/src/components/DatabaseConfigForm.jsx (same for StorageConfigForm).


311-314: useEffect dependency may cause unnecessary rerenders.

This effect runs whenever project changes, which happens on every fetch or optimistic update. If project is a large object, this could cause performance issues.

Consider using a more specific dependency or memoizing the check:

⚡ Optimize dependency
   useEffect(() => {
-    const configured = project?.resources?.db?.isExternal || false;
-    setShowForm(!configured);
-  }, [project]);
+    setShowForm(!(project?.resources?.db?.isExternal || false));
+  }, [project?.resources?.db?.isExternal]);

This way the effect only runs when the actual configuration status changes, not on every project update.


316-336: Add loading state feedback and disable form during submission.

The form doesn't disable inputs while loading is true, allowing users to modify values mid-submission, potentially causing race conditions.

🔒 Disable form during submission
             <input
               type="password"
               className="input-field"
               placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
               value={dbUri}
               onChange={(e) => setDbUri(e.target.value)}
+              disabled={loading}
               style={{

Also consider showing a loading spinner or progress indicator for better UX.

frontend/src/pages/Database.jsx (3)

99-115: Delete confirmation should await deletion (or show pending state) before closing.

Right now onConfirm fires handleDelete(selectedId); and immediately closes the modal (Lines 308-311), which can feel “successful” even if the request fails.

Proposed fix (await + basic guard)
          <ConfirmationModal
            open={showModal}
            title="Delete Record"
            message="Are you sure you want to delete this record? This action cannot be undone."
-           onConfirm={() => {
-             handleDelete(selectedId);
-             setShowModal(false);
-           }}
+           onConfirm={async () => {
+             if (!selectedId) return;
+             await handleDelete(selectedId);
+             setShowModal(false);
+             setSelectedId(null);
+           }}
-           onCancel={() => setShowModal(false)}
+           onCancel={() => {
+             setShowModal(false);
+             setSelectedId(null);
+           }}
          />

Also applies to: 303-313


456-495: Add-document modal: add basic dialog a11y hooks (role/aria + Escape/backdrop behavior).

This overlay/modal structure currently lacks obvious role="dialog", aria-modal="true", and Escape-to-close handling (Lines 456-495). Even a minimal pass would help.


497-861: Consider moving the large inline <style> block to a stylesheet (or Tailwind utilities).

Keeping ~300+ lines of CSS inside the component makes the page harder to maintain and increases the chance of global selector collisions.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba0c02 and 6e1d8c5.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • frontend/index.html
  • frontend/package.json
  • frontend/src/index.css
  • frontend/src/pages/ConfirmationModal.jsx
  • frontend/src/pages/Database.jsx
  • frontend/src/pages/ProjectSettings.jsx
🔇 Additional comments (2)
frontend/src/pages/ConfirmationModal.jsx (1)

4-5: LGTM—clean early return pattern.

The guard clause correctly prevents rendering when the modal is closed.

frontend/src/pages/ProjectSettings.jsx (1)

1-8: LGTM—imports are clean and well-organized.

All imports are used and properly structured. The addition of ConfirmationModal aligns with the PR objectives.

Comment thread frontend/index.html Outdated
Comment thread frontend/package.json
Comment thread frontend/src/pages/ConfirmationModal.jsx
Comment thread frontend/src/pages/ConfirmationModal.jsx Outdated
Comment thread frontend/src/pages/ConfirmationModal.jsx
Comment thread frontend/src/pages/Database.jsx
Comment on lines +117 to +127
const handleAddDocument = async (e) => {
e.preventDefault();
try {
const formattedData = { ...newData };
(activeCollection.model || []).forEach((field) => {
if (field.type === "Number")
formattedData[field.key] = Number(formattedData[field.key]);
if (field.type === "Boolean")
formattedData[field.key] = formattedData[field.key] === "true";
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

handleAddDocument: optional Number/Boolean fields can become NaN/false unintentionally.

For missing/empty optional fields, Number(undefined)NaN and "" === "true"false (Lines 121-126). That can send surprising values to the backend and potentially break validation.

Proposed fix (only coerce when a value is present)
      const formattedData = { ...newData };
      (activeCollection.model || []).forEach((field) => {
+        const raw = formattedData[field.key];
+        if (raw === "" || raw === undefined || raw === null) {
+          delete formattedData[field.key];
+          return;
+        }
         if (field.type === "Number")
-          formattedData[field.key] = Number(formattedData[field.key]);
+          formattedData[field.key] = Number(raw);
         if (field.type === "Boolean")
-          formattedData[field.key] = formattedData[field.key] === "true";
+          formattedData[field.key] = raw === "true";
      });
🤖 Prompt for AI Agents
In @frontend/src/pages/Database.jsx around lines 117 - 127, In
handleAddDocument, avoid coercing empty/missing optional fields which currently
turn into NaN/false; before converting formattedData[field.key] for Number or
Boolean (iterating activeCollection.model), only perform Number(...) or ===
"true" when formattedData[field.key] is not null/undefined and not an empty
string (e.g., check formattedData[field.key] != null && formattedData[field.key]
!== "") so absent values stay undefined/empty instead of becoming NaN/false.

Comment on lines +252 to +317
const TableView = () => (
<div className="table-container fade-in">
<table className="modern-table">
<thead>
<tr>
<th className="w-12">#</th>
{(activeCollection.model || []).map((field) => (
<th key={field.key}>
<div className="th-content">
{field.key}
<span className="type-badge">{field.type}</span>
</div>
</th>
))}
<th>ID</th>
<th className="w-10">Actions</th>
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={row._id} className="table-row">
<td className="text-muted">{i + 1}</td>
{(activeCollection.model || []).map((field) => (
<td key={field.key}>
{typeof row[field.key] === "boolean" ? (
<span
className={`status-badge ${
row[field.key] ? "success" : "danger"
}`}
>
{String(row[field.key])}
</span>
) : (
<span className="cell-text">{String(row[field.key])}</span>
)}
</td>
))}
<td className="font-mono text-xs text-muted">
{row._id.substring(0, 8)}...
</td>
<td>
<button
className="btn-icon danger-hover"
onClick={() => fetchShowModal(row._id)}
>
<Trash2 size={15} />
</button>
</td>
</tr>
))}
</tbody>
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Record"
message="Are you sure you want to delete this record? This action cannot be undone."
onConfirm={() => {
handleDelete(selectedId);
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}
</table>
</div>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Modal is rendered inside <table> (invalid DOM) — move it outside the table element.

ConfirmationModal is a direct child of <table> (Lines 303-314). Even if it visually overlays, this is invalid table structure and can lead to browser DOM “fixups” and weird layout/accessibility behavior.

Proposed fix (render modal as a sibling to the table)
-  const TableView = () => (
-    <div className="table-container fade-in">
-      <table className="modern-table">
+  const TableView = () => (
+    <div className="table-container fade-in">
+      <table className="modern-table">
         <thead>
           ...
         </thead>
         <tbody>
           ...
         </tbody>
-        {showModal && (
-          <ConfirmationModal
-            open={showModal}
-            title="Delete Record"
-            message="Are you sure you want to delete this record? This action cannot be undone."
-            onConfirm={() => {
-              handleDelete(selectedId);
-              setShowModal(false);
-            }}
-            onCancel={() => setShowModal(false)}
-          />
-        )}
       </table>
+      {showModal && (
+        <ConfirmationModal
+          open={showModal}
+          title="Delete Record"
+          message="Are you sure you want to delete this record? This action cannot be undone."
+          onConfirm={async () => {
+            if (!selectedId) return;
+            await handleDelete(selectedId);
+            setShowModal(false);
+            setSelectedId(null);
+          }}
+          onCancel={() => {
+            setShowModal(false);
+            setSelectedId(null);
+          }}
+        />
+      )}
     </div>
   );
🤖 Prompt for AI Agents
In @frontend/src/pages/Database.jsx around lines 252 - 317, The
ConfirmationModal is being rendered as a child of the <table> inside the
TableView component which produces invalid DOM; move the modal out of the table
element so it is a sibling (or placed outside the table container) and keep its
props and handlers intact (showModal, selectedId, handleDelete, setShowModal,
onConfirm/onCancel). Locate the TableView component and remove the
ConfirmationModal from inside the <table> JSX, then render it immediately after
the closing </table> (or at a higher-level sibling container) so the modal
overlays correctly without breaking table structure.

Comment thread frontend/src/pages/ProjectSettings.jsx Outdated
Comment on lines +267 to +297
<button
onClick={() => {
setShowModal(true);
}}
className="btn btn-danger"
disabled={deleteConfirm !== project?.name}
style={{
width: "100%",
justifyContent: "center",
background: "#ea5455",
border: "none",
color: "#fff",
padding: "12px",
borderRadius: "8px",
marginTop: "10px",
}}
>
<Trash2 size={18} /> Permanently Delete Project
</button>
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Project"
message="Are you sure you want to delete this project? This action cannot be undone."
onConfirm={() => {
handleDeleteProject();
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve modal confirmation UX flow.

The current flow requires users to:

  1. Type the project name in the input (line 254)
  2. Click "Permanently Delete Project" button (line 267)
  3. Confirm again in the modal (line 291)

This creates redundant confirmation—the typed name confirmation already provides sufficient friction. The modal adds little value here and frustrates users.

Recommendation: Either remove the modal OR remove the text input requirement. Don't require both.

💡 Suggested UX improvements

Option 1 (Recommended): Keep the text input, remove the modal:

           <button
-            onClick={() => {
-              setShowModal(true);
-            }}
+            onClick={handleDeleteProject}
             className="btn btn-danger"
             disabled={deleteConfirm !== project?.name}
           >
             <Trash2 size={18} /> Permanently Delete Project
           </button>
-          {showModal && (
-            <ConfirmationModal
-              open={showModal}
-              title="Delete Project"
-              message="Are you sure you want to delete this project? This action cannot be undone."
-              onConfirm={() => {
-                handleDeleteProject();
-                setShowModal(false);
-              }}
-              onCancel={() => setShowModal(false)}
-            />
-          )}

Option 2: Remove text input, keep modal for visual confirmation:

Remove lines 233-265 (the input field and its label), and keep the modal as the sole confirmation mechanism.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={() => {
setShowModal(true);
}}
className="btn btn-danger"
disabled={deleteConfirm !== project?.name}
style={{
width: "100%",
justifyContent: "center",
background: "#ea5455",
border: "none",
color: "#fff",
padding: "12px",
borderRadius: "8px",
marginTop: "10px",
}}
>
<Trash2 size={18} /> Permanently Delete Project
</button>
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Project"
message="Are you sure you want to delete this project? This action cannot be undone."
onConfirm={() => {
handleDeleteProject();
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}
<button
onClick={handleDeleteProject}
className="btn btn-danger"
disabled={deleteConfirm !== project?.name}
style={{
width: "100%",
justifyContent: "center",
background: "#ea5455",
border: "none",
color: "#fff",
padding: "12px",
borderRadius: "8px",
marginTop: "10px",
}}
>
<Trash2 size={18} /> Permanently Delete Project
</button>
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 267 - 297, Remove the
redundant confirmation modal and its state: keep the typed-name safety check
(deleteConfirm vs project?.name) and call handleDeleteProject directly from the
"Permanently Delete Project" button onClick; remove showModal state,
setShowModal usages, the ConfirmationModal component usage and its props (open,
title, message, onConfirm, onCancel), and any related imports so the button
executes handleDeleteProject immediately when enabled while still being disabled
unless deleteConfirm === project?.name.

@vercel

vercel Bot commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
ur-backend Ready Ready Preview, Comment Jan 9, 2026 5:36am

@yash-pouranik

Copy link
Copy Markdown
Member

@tejaavula076 yes its working properly u can check here - https://ur-backend-git-fork-tejaavula076-9b5337-yash-pouraniks-projects.vercel.app/

but make sure the theme of the delete component should be same as the project theme.
and then will merge it.

and one check is failing of backend test but i think thats from my side, will look into it and comment here.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @frontend/src/pages/ProjectSettings.jsx:
- Around line 44-63: The catch block in handleRename swallows errors; update it
to catch the error (e.g., catch (err)) and log the actual error details (for
example using console.error with a descriptive message like "Failed to rename
project") in addition to showing the toast, so debugging information is
preserved while keeping the existing toast and state updates.
- Around line 65-83: In handleDeleteProject remove the leftover commented-out
window.confirm block (the lines around the old confirm call) so the modal-only
flow remains clean, and update the catch of the axios.delete call to accept an
error parameter and log it (e.g., console.error or logger.error) and include the
error information when calling toast.error so failures are recorded and easier
to debug; ensure changes are applied inside the handleDeleteProject function
that performs the axios.delete request.
- Around line 304-467: The catch in DatabaseConfigForm.handleUpdate currently
swallows errors; update it to log the caught error for debugging (e.g.,
console.error(err) and/or send to your error tracking service) in addition to
showing the toast (keep the existing toast.error(err.response?.data?.error ||
"Failed to update DB config")). Ensure you reference the err variable inside the
catch before finally and do not remove existing UI behavior.
- Around line 26-42: The catch block in the fetchProject async function (inside
useEffect) swallows errors; update fetchProject to accept the error parameter
(e.g., catch (err)) and log the error (console.error(err) or a centralized
logger) when a request fails, while still calling toast.error("Failed to load
project"), so setProject/setNewName logic stays unchanged and setLoading(false)
remains in finally.
- Around line 469-710: The catch block in StorageConfigForm.handleUpdate only
shows a toast and doesn't log the error; update that catch to log the full error
for debugging (e.g., console.error or a configured logger) including err and
err.response?.data (and err.message) so you capture API response details and
stack traces while still showing the existing toast; locate the catch inside the
async handleUpdate function and add the logging before the toast/error-handling
lines.
🧹 Nitpick comments (1)
frontend/src/pages/ProjectSettings.jsx (1)

286-297: Simplify modal rendering logic.

The modal is both conditionally rendered ({showModal && ...}) and receives open={showModal}. This is redundant and prevents smooth animations/transitions.

♻️ Recommended approach

Remove the conditional rendering and let the modal's open prop control visibility:

           <Trash2 size={18} /> Permanently Delete Project
         </button>
-        {showModal && (
-          <ConfirmationModal
-            open={showModal}
-            title="Delete Project"
-            message="Are you sure you want to delete this project? This action cannot be undone."
-            onConfirm={() => {
-              handleDeleteProject();
-              setShowModal(false);
-            }}
-            onCancel={() => setShowModal(false)}
-          />
-        )}
+        <ConfirmationModal
+          open={showModal}
+          title="Delete Project"
+          message="Are you sure you want to delete this project? This action cannot be undone."
+          onConfirm={() => {
+            handleDeleteProject();
+            setShowModal(false);
+          }}
+          onCancel={() => setShowModal(false)}
+        />
       </div>
     </div>

This allows the ConfirmationModal to handle enter/exit transitions smoothly.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e1d8c5 and 254bb69.

📒 Files selected for processing (1)
  • frontend/src/pages/ProjectSettings.jsx
🔇 Additional comments (2)
frontend/src/pages/ProjectSettings.jsx (2)

1-8: LGTM!

Imports are clean and all dependencies are properly used throughout the component.


11-24: LGTM!

State declarations are well-organized and clearly commented.

Comment on lines +26 to +42
useEffect(() => {
const fetchProject = async () => {
try {
const res = await axios.get(`${API_URL}/api/projects/${projectId}`, {
headers: { Authorization: `Bearer ${token}` },
});
setProject(res.data);
// Set initial name for renaming
setNewName(res.data.name);
} catch {
toast.error("Failed to load project");
} finally {
setLoading(false);
}
};
fetchProject();
}, [projectId, token]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Log errors in catch blocks for debugging.

The catch block swallows the error after displaying a toast. In production, this makes debugging failed requests difficult.

🔍 Proposed fix
       } catch {
         toast.error("Failed to load project");
+        console.error("Failed to load project:", error);
       } finally {

Also update the catch parameter:

-      } catch {
+      } catch (error) {
         toast.error("Failed to load project");
+        console.error("Failed to load project:", error);
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 26 - 42, The catch block
in the fetchProject async function (inside useEffect) swallows errors; update
fetchProject to accept the error parameter (e.g., catch (err)) and log the error
(console.error(err) or a centralized logger) when a request fails, while still
calling toast.error("Failed to load project"), so setProject/setNewName logic
stays unchanged and setLoading(false) remains in finally.

Comment on lines +44 to +63
// --- NEW: HANDLE RENAME ---
const handleRename = async () => {
if (!newName.trim()) return toast.error("Project name cannot be empty");

{/* General Settings (Rename Feature) */}
<div className="card" style={{ marginBottom: '2rem' }}>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1.5rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{ width: '6px', height: '24px', background: 'var(--color-primary)', borderRadius: '4px' }}></div>
General Information
</h3>
<div className="form-group" style={{ display: 'flex', gap: '15px', alignItems: 'flex-end', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: '250px' }}>
<label className="form-label" style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--color-text-muted)' }}>Project Name</label>
<input
type="text"
className="input-field"
value={newName}
onChange={(e) => setNewName(e.target.value)}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<button
onClick={handleRename}
className="btn btn-primary"
disabled={renaming || newName === project?.name}
style={{ padding: '12px 24px', height: '45px' }}
>
{renaming ? 'Saving...' : 'Save Changes'}
</button>
</div>
</div>
setRenaming(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}`,
{ name: newName },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Project renamed successfully!");
// Update local state to reflect change immediately
setProject((prev) => ({ ...prev, name: newName }));
} catch {
toast.error("Failed to rename project");
} finally {
setRenaming(false);
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Log errors in catch blocks for debugging.

Same issue as in the fetch function - the catch block should log the actual error for debugging purposes.

🔍 Proposed fix
-    } catch {
+    } catch (error) {
       toast.error("Failed to rename project");
+      console.error("Failed to rename project:", error);
     } finally {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// --- NEW: HANDLE RENAME ---
const handleRename = async () => {
if (!newName.trim()) return toast.error("Project name cannot be empty");
{/* General Settings (Rename Feature) */}
<div className="card" style={{ marginBottom: '2rem' }}>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1.5rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{ width: '6px', height: '24px', background: 'var(--color-primary)', borderRadius: '4px' }}></div>
General Information
</h3>
<div className="form-group" style={{ display: 'flex', gap: '15px', alignItems: 'flex-end', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: '250px' }}>
<label className="form-label" style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--color-text-muted)' }}>Project Name</label>
<input
type="text"
className="input-field"
value={newName}
onChange={(e) => setNewName(e.target.value)}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<button
onClick={handleRename}
className="btn btn-primary"
disabled={renaming || newName === project?.name}
style={{ padding: '12px 24px', height: '45px' }}
>
{renaming ? 'Saving...' : 'Save Changes'}
</button>
</div>
</div>
setRenaming(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}`,
{ name: newName },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Project renamed successfully!");
// Update local state to reflect change immediately
setProject((prev) => ({ ...prev, name: newName }));
} catch {
toast.error("Failed to rename project");
} finally {
setRenaming(false);
}
};
// --- NEW: HANDLE RENAME ---
const handleRename = async () => {
if (!newName.trim()) return toast.error("Project name cannot be empty");
setRenaming(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}`,
{ name: newName },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Project renamed successfully!");
// Update local state to reflect change immediately
setProject((prev) => ({ ...prev, name: newName }));
} catch (error) {
toast.error("Failed to rename project");
console.error("Failed to rename project:", error);
} finally {
setRenaming(false);
}
};
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 44 - 63, The catch block
in handleRename swallows errors; update it to catch the error (e.g., catch
(err)) and log the actual error details (for example using console.error with a
descriptive message like "Failed to rename project") in addition to showing the
toast, so debugging information is preserved while keeping the existing toast
and state updates.

Comment on lines +65 to +83
const handleDeleteProject = async () => {
if (deleteConfirm !== project.name)
return toast.error("Project name does not match");

{/* Danger Zone */}
<div className="card" style={{ border: '1px solid rgba(234, 84, 85, 0.3)', background: 'rgba(234, 84, 85, 0.02)' }}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', marginBottom: '1.5rem', color: '#ea5455' }}>
<AlertTriangle size={24} />
<h3 style={{ fontSize: '1.1rem', fontWeight: 600 }}>Danger Zone</h3>
</div>

<p style={{ color: 'var(--color-text-muted)', marginBottom: '1.5rem', fontSize: '0.9rem', lineHeight: '1.6' }}>
This action cannot be undone. This will permanently delete the
<strong style={{ color: '#fff' }}> {project?.name}</strong> project and all associated data including collections, files, and users.
</p>

<div style={{ maxWidth: '500px' }}>
<div className="form-group" style={{ marginBottom: '1rem' }}>
<label className="form-label" style={{ color: '#ea5455', fontWeight: 500, marginBottom: '8px', display: 'block' }}>
Type <strong style={{ textDecoration: 'underline' }}>{project?.name}</strong> to confirm
</label>
<input
type="text"
className="input-field"
placeholder={project?.name}
value={deleteConfirm}
onChange={(e) => setDeleteConfirm(e.target.value)}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid rgba(234, 84, 85, 0.3)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<button
onClick={handleDeleteProject}
className="btn btn-danger"
disabled={deleteConfirm !== project?.name}
style={{ width: '100%', justifyContent: 'center', background: '#ea5455', border: 'none', color: '#fff', padding: '12px', borderRadius: '8px', marginTop: '10px' }}
>
<Trash2 size={18} /> Permanently Delete Project
</button>
</div>
</div>
// // if (
// // !confirm("Final warning: This will delete the project and all its data.")
// // )
// return;

try {
await axios.delete(`${API_URL}/api/projects/${projectId}`, {
headers: { Authorization: `Bearer ${token}` },
});
toast.success("Project deleted");
navigate("/dashboard");
} catch {
toast.error("Failed to delete project");
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove commented-out code and log errors.

Two issues:

  1. Lines 69-72 contain commented-out code that should be removed since the window.confirm has been replaced with the modal.
  2. The catch block should log the error for debugging.
🧹 Proposed fix
   const handleDeleteProject = async () => {
     if (deleteConfirm !== project.name)
       return toast.error("Project name does not match");
 
-    // // if (
-    // //   !confirm("Final warning: This will delete the project and all its data.")
-    // // )
-    //   return;
-
     try {
       await axios.delete(`${API_URL}/api/projects/${projectId}`, {
         headers: { Authorization: `Bearer ${token}` },
       });
       toast.success("Project deleted");
       navigate("/dashboard");
-    } catch {
+    } catch (error) {
       toast.error("Failed to delete project");
+      console.error("Failed to delete project:", error);
     }
   };
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 65 - 83, In
handleDeleteProject remove the leftover commented-out window.confirm block (the
lines around the old confirm call) so the modal-only flow remains clean, and
update the catch of the axios.delete call to accept an error parameter and log
it (e.g., console.error or logger.error) and include the error information when
calling toast.error so failures are recorded and easier to debug; ensure changes
are applied inside the handleDeleteProject function that performs the
axios.delete request.

Comment on lines 304 to 467
function DatabaseConfigForm({ project, projectId, token }) {
const [dbUri, setDbUri] = useState('');
const [loading, setLoading] = useState(false);
// Use optional chaining carefully - project might be null initially
const isConfigured = project?.resources?.db?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);

useEffect(() => {
const configured = project?.resources?.db?.isExternal || false;
setShowForm(!configured);
}, [project]);

const handleUpdate = async () => {
if (!dbUri) return toast.error("Database URI is required");

setLoading(true);
try {
await axios.patch(`${API_URL}/api/projects/${projectId}/byod-config`,
{ dbUri },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Database configuration updated!");
setShowForm(false);
setDbUri('');
// Typically we'd reload project data here, but for now we rely on user refresh or optimistic ui if needed
// Ideally notify parent to refresh project, but basic flow:
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update DB config");
} finally {
setLoading(false);
}
};
const [dbUri, setDbUri] = useState("");
const [loading, setLoading] = useState(false);
// Use optional chaining carefully - project might be null initially
const isConfigured = project?.resources?.db?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);

return (
<div className="card" style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, width: '4px', height: '100%', background: 'var(--color-primary)' }}></div>

<div style={{ marginBottom: '1rem' }}>
<h3 style={{ fontSize: '1.1rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Save size={20} color="var(--color-primary)" /> Database Configuration (MongoDB)
</h3>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.9rem', marginTop: '5px' }}>
Connect your own MongoDB cluster.
</p>
</div>
useEffect(() => {
const configured = project?.resources?.db?.isExternal || false;
setShowForm(!configured);
}, [project]);

const handleUpdate = async () => {
if (!dbUri) return toast.error("Database URI is required");

setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
{ dbUri },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Database configuration updated!");
setShowForm(false);
setDbUri("");
// Typically we'd reload project data here, but for now we rely on user refresh or optimistic ui if needed
// Ideally notify parent to refresh project, but basic flow:
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update DB config");
} finally {
setLoading(false);
}
};

return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "var(--color-primary)",
}}
></div>

{isConfigured && !showForm ? (
<div style={{ marginTop: '1rem', background: 'rgba(16, 185, 129, 0.1)', padding: '1rem', borderRadius: '8px', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', color: '#10B981', fontWeight: 600, marginBottom: '0.5rem' }}>
<CheckCircle size={18} />
Connected
</div>
<button className="btn btn-secondary btn-sm" onClick={() => setShowForm(true)} style={{ borderColor: 'var(--color-border)', color: 'var(--color-text-main)' }}>
Update URI
</button>
</div>
) : (
<div style={{ marginTop: '1rem' }}>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>MongoDB Connection URI</label>
<input
type="password"
className="input-field"
placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
value={dbUri}
onChange={(e) => setDbUri(e.target.value)}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff', fontFamily: 'monospace' }}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '1rem' }}>
{isConfigured && <button className="btn btn-ghost" onClick={() => setShowForm(false)}>Cancel</button>}
<button onClick={handleUpdate} className="btn btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Connect Database'}
</button>
</div>
</div>
<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="var(--color-primary)" /> Database Configuration
(MongoDB)
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own MongoDB cluster.
</p>
</div>

{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update URI
</button>
</div>
) : (
<div style={{ marginTop: "1rem" }}>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
MongoDB Connection URI
</label>
<input
type="password"
className="input-field"
placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
value={dbUri}
onChange={(e) => setDbUri(e.target.value)}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Database"}
</button>
</div>
</div>
);
)}
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Log errors in catch blocks for debugging.

The handleUpdate function has an empty catch block (line 331) that swallows the error. Add error logging for production debugging.

🔍 Proposed fix
     } catch (err) {
       toast.error(err.response?.data?.error || "Failed to update DB config");
+      console.error("Failed to update DB config:", err);
     } finally {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function DatabaseConfigForm({ project, projectId, token }) {
const [dbUri, setDbUri] = useState('');
const [loading, setLoading] = useState(false);
// Use optional chaining carefully - project might be null initially
const isConfigured = project?.resources?.db?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
useEffect(() => {
const configured = project?.resources?.db?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleUpdate = async () => {
if (!dbUri) return toast.error("Database URI is required");
setLoading(true);
try {
await axios.patch(`${API_URL}/api/projects/${projectId}/byod-config`,
{ dbUri },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Database configuration updated!");
setShowForm(false);
setDbUri('');
// Typically we'd reload project data here, but for now we rely on user refresh or optimistic ui if needed
// Ideally notify parent to refresh project, but basic flow:
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update DB config");
} finally {
setLoading(false);
}
};
const [dbUri, setDbUri] = useState("");
const [loading, setLoading] = useState(false);
// Use optional chaining carefully - project might be null initially
const isConfigured = project?.resources?.db?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
return (
<div className="card" style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, width: '4px', height: '100%', background: 'var(--color-primary)' }}></div>
<div style={{ marginBottom: '1rem' }}>
<h3 style={{ fontSize: '1.1rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Save size={20} color="var(--color-primary)" /> Database Configuration (MongoDB)
</h3>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.9rem', marginTop: '5px' }}>
Connect your own MongoDB cluster.
</p>
</div>
useEffect(() => {
const configured = project?.resources?.db?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleUpdate = async () => {
if (!dbUri) return toast.error("Database URI is required");
setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
{ dbUri },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Database configuration updated!");
setShowForm(false);
setDbUri("");
// Typically we'd reload project data here, but for now we rely on user refresh or optimistic ui if needed
// Ideally notify parent to refresh project, but basic flow:
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update DB config");
} finally {
setLoading(false);
}
};
return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "var(--color-primary)",
}}
></div>
{isConfigured && !showForm ? (
<div style={{ marginTop: '1rem', background: 'rgba(16, 185, 129, 0.1)', padding: '1rem', borderRadius: '8px', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', color: '#10B981', fontWeight: 600, marginBottom: '0.5rem' }}>
<CheckCircle size={18} />
Connected
</div>
<button className="btn btn-secondary btn-sm" onClick={() => setShowForm(true)} style={{ borderColor: 'var(--color-border)', color: 'var(--color-text-main)' }}>
Update URI
</button>
</div>
) : (
<div style={{ marginTop: '1rem' }}>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>MongoDB Connection URI</label>
<input
type="password"
className="input-field"
placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
value={dbUri}
onChange={(e) => setDbUri(e.target.value)}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff', fontFamily: 'monospace' }}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '1rem' }}>
{isConfigured && <button className="btn btn-ghost" onClick={() => setShowForm(false)}>Cancel</button>}
<button onClick={handleUpdate} className="btn btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Connect Database'}
</button>
</div>
</div>
<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="var(--color-primary)" /> Database Configuration
(MongoDB)
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own MongoDB cluster.
</p>
</div>
{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update URI
</button>
</div>
) : (
<div style={{ marginTop: "1rem" }}>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
MongoDB Connection URI
</label>
<input
type="password"
className="input-field"
placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
value={dbUri}
onChange={(e) => setDbUri(e.target.value)}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Database"}
</button>
</div>
</div>
);
)}
</div>
);
}
function DatabaseConfigForm({ project, projectId, token }) {
const [dbUri, setDbUri] = useState("");
const [loading, setLoading] = useState(false);
// Use optional chaining carefully - project might be null initially
const isConfigured = project?.resources?.db?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
useEffect(() => {
const configured = project?.resources?.db?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleUpdate = async () => {
if (!dbUri) return toast.error("Database URI is required");
setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
{ dbUri },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Database configuration updated!");
setShowForm(false);
setDbUri("");
// Typically we'd reload project data here, but for now we rely on user refresh or optimistic ui if needed
// Ideally notify parent to refresh project, but basic flow:
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update DB config");
console.error("Failed to update DB config:", err);
} finally {
setLoading(false);
}
};
return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "var(--color-primary)",
}}
></div>
<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="var(--color-primary)" /> Database Configuration
(MongoDB)
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own MongoDB cluster.
</p>
</div>
{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update URI
</button>
</div>
) : (
<div style={{ marginTop: "1rem" }}>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
MongoDB Connection URI
</label>
<input
type="password"
className="input-field"
placeholder="mongodb+srv://user:pass@cluster.mongodb.net/..."
value={dbUri}
onChange={(e) => setDbUri(e.target.value)}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Database"}
</button>
</div>
</div>
)}
</div>
);
}
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 304 - 467, The catch in
DatabaseConfigForm.handleUpdate currently swallows errors; update it to log the
caught error for debugging (e.g., console.error(err) and/or send to your error
tracking service) in addition to showing the toast (keep the existing
toast.error(err.response?.data?.error || "Failed to update DB config")). Ensure
you reference the err variable inside the catch before finally and do not remove
existing UI behavior.

Comment on lines 469 to +710
function StorageConfigForm({ project, projectId, token }) {
const [config, setConfig] = useState({
storageUrl: '',
storageKey: '',
storageProvider: 'supabase'
});
const [loading, setLoading] = useState(false);
const isConfigured = project?.resources?.storage?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);

useEffect(() => {
const configured = project?.resources?.storage?.isExternal || false;
setShowForm(!configured);
}, [project]);

const handleChange = (e) => setConfig({ ...config, [e.target.name]: e.target.value });

const handleUpdate = async () => {
if (!config.storageUrl || !config.storageKey) return toast.error("URL and Key are required");

setLoading(true);
try {
await axios.patch(`${API_URL}/api/projects/${projectId}/byod-config`,
config,
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Storage configuration updated!");
setShowForm(false);
setConfig({ storageUrl: '', storageKey: '', storageProvider: 'supabase' });
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update Storage config");
} finally {
setLoading(false);
}
};
const [config, setConfig] = useState({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
const [loading, setLoading] = useState(false);
const isConfigured = project?.resources?.storage?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);

return (
<div className="card" style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, width: '4px', height: '100%', background: '#34d399' }}></div>

<div style={{ marginBottom: '1rem' }}>
<h3 style={{ fontSize: '1.1rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Save size={20} color="#34d399" /> Storage Configuration
</h3>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.9rem', marginTop: '5px' }}>
Connect your own S3-compatible storage.
</p>
</div>
useEffect(() => {
const configured = project?.resources?.storage?.isExternal || false;
setShowForm(!configured);
}, [project]);

const handleChange = (e) =>
setConfig({ ...config, [e.target.name]: e.target.value });

{isConfigured && !showForm ? (
<div style={{ marginTop: '1rem', background: 'rgba(16, 185, 129, 0.1)', padding: '1rem', borderRadius: '8px', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', color: '#10B981', fontWeight: 600, marginBottom: '0.5rem' }}>
<CheckCircle size={18} />
Connected
</div>
<button className="btn btn-secondary btn-sm" onClick={() => setShowForm(true)} style={{ borderColor: 'var(--color-border)', color: 'var(--color-text-main)' }}>
Update Config
</button>
</div>
) : (
<div style={{ marginTop: '1rem', display: 'grid', gap: '1.5rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Storage URL</label>
<input
type="text"
name="storageUrl"
className="input-field"
value={config.storageUrl}
onChange={handleChange}
placeholder="https://..."
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Provider</label>
<select
name="storageProvider"
className="input-field"
value={config.storageProvider}
onChange={handleChange}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
>
<option value="supabase">Supabase</option>
<option value="aws" disabled>AWS S3 (Coming Soon)</option>
</select>
</div>
</div>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Storage Key / Service Role</label>
<input
type="password"
name="storageKey"
className="input-field"
value={config.storageKey}
onChange={handleChange}
placeholder="Key..."
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff', fontFamily: 'monospace' }}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '1rem' }}>
{isConfigured && <button className="btn btn-ghost" onClick={() => setShowForm(false)}>Cancel</button>}
<button onClick={handleUpdate} className="btn btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Connect Storage'}
</button>
</div>
</div>
const handleUpdate = async () => {
if (!config.storageUrl || !config.storageKey)
return toast.error("URL and Key are required");

setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
config,
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Storage configuration updated!");
setShowForm(false);
setConfig({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
} catch (err) {
toast.error(
err.response?.data?.error || "Failed to update Storage config"
);
} finally {
setLoading(false);
}
};

return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "#34d399",
}}
></div>

<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="#34d399" /> Storage Configuration
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own S3-compatible storage.
</p>
</div>

{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update Config
</button>
</div>
) : (
<div style={{ marginTop: "1rem", display: "grid", gap: "1.5rem" }}>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "1.5rem",
}}
>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage URL
</label>
<input
type="text"
name="storageUrl"
className="input-field"
value={config.storageUrl}
onChange={handleChange}
placeholder="https://..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
/>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Provider
</label>
<select
name="storageProvider"
className="input-field"
value={config.storageProvider}
onChange={handleChange}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
>
<option value="supabase">Supabase</option>
<option value="aws" disabled>
AWS S3 (Coming Soon)
</option>
</select>
</div>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage Key / Service Role
</label>
<input
type="password"
name="storageKey"
className="input-field"
value={config.storageKey}
onChange={handleChange}
placeholder="Key..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Storage"}
</button>
</div>
</div>
);
} No newline at end of file
)}
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Log errors in catch blocks for debugging.

The handleUpdate function has an empty catch block (line 505) that only shows a toast. Add error logging for production debugging.

🔍 Proposed fix
     } catch (err) {
       toast.error(
         err.response?.data?.error || "Failed to update Storage config"
       );
+      console.error("Failed to update Storage config:", err);
     } finally {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function StorageConfigForm({ project, projectId, token }) {
const [config, setConfig] = useState({
storageUrl: '',
storageKey: '',
storageProvider: 'supabase'
});
const [loading, setLoading] = useState(false);
const isConfigured = project?.resources?.storage?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
useEffect(() => {
const configured = project?.resources?.storage?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleChange = (e) => setConfig({ ...config, [e.target.name]: e.target.value });
const handleUpdate = async () => {
if (!config.storageUrl || !config.storageKey) return toast.error("URL and Key are required");
setLoading(true);
try {
await axios.patch(`${API_URL}/api/projects/${projectId}/byod-config`,
config,
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Storage configuration updated!");
setShowForm(false);
setConfig({ storageUrl: '', storageKey: '', storageProvider: 'supabase' });
} catch (err) {
toast.error(err.response?.data?.error || "Failed to update Storage config");
} finally {
setLoading(false);
}
};
const [config, setConfig] = useState({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
const [loading, setLoading] = useState(false);
const isConfigured = project?.resources?.storage?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
return (
<div className="card" style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, width: '4px', height: '100%', background: '#34d399' }}></div>
<div style={{ marginBottom: '1rem' }}>
<h3 style={{ fontSize: '1.1rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Save size={20} color="#34d399" /> Storage Configuration
</h3>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.9rem', marginTop: '5px' }}>
Connect your own S3-compatible storage.
</p>
</div>
useEffect(() => {
const configured = project?.resources?.storage?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleChange = (e) =>
setConfig({ ...config, [e.target.name]: e.target.value });
{isConfigured && !showForm ? (
<div style={{ marginTop: '1rem', background: 'rgba(16, 185, 129, 0.1)', padding: '1rem', borderRadius: '8px', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', color: '#10B981', fontWeight: 600, marginBottom: '0.5rem' }}>
<CheckCircle size={18} />
Connected
</div>
<button className="btn btn-secondary btn-sm" onClick={() => setShowForm(true)} style={{ borderColor: 'var(--color-border)', color: 'var(--color-text-main)' }}>
Update Config
</button>
</div>
) : (
<div style={{ marginTop: '1rem', display: 'grid', gap: '1.5rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Storage URL</label>
<input
type="text"
name="storageUrl"
className="input-field"
value={config.storageUrl}
onChange={handleChange}
placeholder="https://..."
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Provider</label>
<select
name="storageProvider"
className="input-field"
value={config.storageProvider}
onChange={handleChange}
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff' }}
>
<option value="supabase">Supabase</option>
<option value="aws" disabled>AWS S3 (Coming Soon)</option>
</select>
</div>
</div>
<div className="form-group">
<label className="form-label" style={{ marginBottom: '8px', display: 'block', fontSize: '0.9rem' }}>Storage Key / Service Role</label>
<input
type="password"
name="storageKey"
className="input-field"
value={config.storageKey}
onChange={handleChange}
placeholder="Key..."
style={{ width: '100%', padding: '12px', background: 'var(--color-bg-input)', border: '1px solid var(--color-border)', borderRadius: '8px', color: '#fff', fontFamily: 'monospace' }}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '1rem' }}>
{isConfigured && <button className="btn btn-ghost" onClick={() => setShowForm(false)}>Cancel</button>}
<button onClick={handleUpdate} className="btn btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Connect Storage'}
</button>
</div>
</div>
const handleUpdate = async () => {
if (!config.storageUrl || !config.storageKey)
return toast.error("URL and Key are required");
setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
config,
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Storage configuration updated!");
setShowForm(false);
setConfig({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
} catch (err) {
toast.error(
err.response?.data?.error || "Failed to update Storage config"
);
} finally {
setLoading(false);
}
};
return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "#34d399",
}}
></div>
<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="#34d399" /> Storage Configuration
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own S3-compatible storage.
</p>
</div>
{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update Config
</button>
</div>
) : (
<div style={{ marginTop: "1rem", display: "grid", gap: "1.5rem" }}>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "1.5rem",
}}
>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage URL
</label>
<input
type="text"
name="storageUrl"
className="input-field"
value={config.storageUrl}
onChange={handleChange}
placeholder="https://..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
/>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Provider
</label>
<select
name="storageProvider"
className="input-field"
value={config.storageProvider}
onChange={handleChange}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
>
<option value="supabase">Supabase</option>
<option value="aws" disabled>
AWS S3 (Coming Soon)
</option>
</select>
</div>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage Key / Service Role
</label>
<input
type="password"
name="storageKey"
className="input-field"
value={config.storageKey}
onChange={handleChange}
placeholder="Key..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Storage"}
</button>
</div>
</div>
);
}
\ No newline at end of file
)}
</div>
);
}
function StorageConfigForm({ project, projectId, token }) {
const [config, setConfig] = useState({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
const [loading, setLoading] = useState(false);
const isConfigured = project?.resources?.storage?.isExternal || false;
const [showForm, setShowForm] = useState(!isConfigured);
useEffect(() => {
const configured = project?.resources?.storage?.isExternal || false;
setShowForm(!configured);
}, [project]);
const handleChange = (e) =>
setConfig({ ...config, [e.target.name]: e.target.value });
const handleUpdate = async () => {
if (!config.storageUrl || !config.storageKey)
return toast.error("URL and Key are required");
setLoading(true);
try {
await axios.patch(
`${API_URL}/api/projects/${projectId}/byod-config`,
config,
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success("Storage configuration updated!");
setShowForm(false);
setConfig({
storageUrl: "",
storageKey: "",
storageProvider: "supabase",
});
} catch (err) {
toast.error(
err.response?.data?.error || "Failed to update Storage config"
);
console.error("Failed to update Storage config:", err);
} finally {
setLoading(false);
}
};
return (
<div className="card" style={{ position: "relative", overflow: "hidden" }}>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "4px",
height: "100%",
background: "#34d399",
}}
></div>
<div style={{ marginBottom: "1rem" }}>
<h3
style={{
fontSize: "1.1rem",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Save size={20} color="#34d399" /> Storage Configuration
</h3>
<p
style={{
color: "var(--color-text-muted)",
fontSize: "0.9rem",
marginTop: "5px",
}}
>
Connect your own S3-compatible storage.
</p>
</div>
{isConfigured && !showForm ? (
<div
style={{
marginTop: "1rem",
background: "rgba(16, 185, 129, 0.1)",
padding: "1rem",
borderRadius: "8px",
border: "1px solid rgba(16, 185, 129, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
color: "#10B981",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
<CheckCircle size={18} />
Connected
</div>
<button
className="btn btn-secondary btn-sm"
onClick={() => setShowForm(true)}
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-main)",
}}
>
Update Config
</button>
</div>
) : (
<div style={{ marginTop: "1rem", display: "grid", gap: "1.5rem" }}>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "1.5rem",
}}
>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage URL
</label>
<input
type="text"
name="storageUrl"
className="input-field"
value={config.storageUrl}
onChange={handleChange}
placeholder="https://..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
/>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Provider
</label>
<select
name="storageProvider"
className="input-field"
value={config.storageProvider}
onChange={handleChange}
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
}}
>
<option value="supabase">Supabase</option>
<option value="aws" disabled>
AWS S3 (Coming Soon)
</option>
</select>
</div>
</div>
<div className="form-group">
<label
className="form-label"
style={{
marginBottom: "8px",
display: "block",
fontSize: "0.9rem",
}}
>
Storage Key / Service Role
</label>
<input
type="password"
name="storageKey"
className="input-field"
value={config.storageKey}
onChange={handleChange}
placeholder="Key..."
style={{
width: "100%",
padding: "12px",
background: "var(--color-bg-input)",
border: "1px solid var(--color-border)",
borderRadius: "8px",
color: "#fff",
fontFamily: "monospace",
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: "12px",
marginTop: "1rem",
}}
>
{isConfigured && (
<button
className="btn btn-ghost"
onClick={() => setShowForm(false)}
>
Cancel
</button>
)}
<button
onClick={handleUpdate}
className="btn btn-primary"
disabled={loading}
>
{loading ? "Saving..." : "Connect Storage"}
</button>
</div>
</div>
)}
</div>
);
}
🤖 Prompt for AI Agents
In @frontend/src/pages/ProjectSettings.jsx around lines 469 - 710, The catch
block in StorageConfigForm.handleUpdate only shows a toast and doesn't log the
error; update that catch to log the full error for debugging (e.g.,
console.error or a configured logger) including err and err.response?.data (and
err.message) so you capture API response details and stack traces while still
showing the existing toast; locate the catch inside the async handleUpdate
function and add the logging before the toast/error-handling lines.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @frontend/src/pages/Database.jsx:
- Around line 303-315: The ConfirmationModal component is incorrectly rendered
as a child of the table which causes invalid DOM nesting; move the JSX block
that renders ConfirmationModal (the conditional that uses showModal, onConfirm
calling handleDelete(selectedId) and setShowModal(false), and onCancel calling
setShowModal(false)) out of the <table> element and place it as a sibling (e.g.,
immediately after the closing </table>) so the modal is mounted at the top-level
of the component tree rather than inside the table.
🧹 Nitpick comments (2)
frontend/src/pages/Database.jsx (2)

42-45: Consider renaming fetchShowModal for clarity.

The fetch prefix typically implies asynchronous data fetching. A name like openDeleteModal or confirmDelete would better convey the function's purpose.

✏️ Suggested rename
-  const fetchShowModal = (id) => {
+  const openDeleteModal = (id) => {
     setShowModal(true);
     setSelectedId(id);
   };

And update the call site at line 295:

-  onClick={() => fetchShowModal(row._id)}
+  onClick={() => openDeleteModal(row._id)}

188-250: Consider extracting inline components.

Sidebar, TableView, JsonView, and SkeletonLoader are defined inside the component body and recreated on every render. For this component size, the impact is minimal, but extracting them to separate files or memoizing them could improve maintainability and performance if the component grows.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 254bb69 and a61ae80.

📒 Files selected for processing (5)
  • frontend/index.html
  • frontend/src/index.css
  • frontend/src/pages/ConfirmationModal.jsx
  • frontend/src/pages/Database.jsx
  • frontend/vite.config.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/pages/ConfirmationModal.jsx
  • frontend/src/index.css
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/Database.jsx (9)
frontend/src/pages/ProjectDetails.jsx (4)
  • useParams (13-13)
  • navigate (14-14)
  • useAuth (15-15)
  • project (17-17)
frontend/src/components/Layout/Sidebar.jsx (2)
  • useParams (10-10)
  • useAuth (11-11)
frontend/src/pages/NotFound.jsx (1)
  • navigate (6-6)
frontend/src/pages/CreateProject.jsx (2)
  • navigate (17-17)
  • useAuth (16-16)
frontend/src/pages/Dashboard.jsx (2)
  • navigate (13-13)
  • useAuth (12-12)
frontend/src/context/AuthContext.jsx (3)
  • useAuth (38-38)
  • useAuth (38-38)
  • token (6-6)
frontend/src/components/Layout/Header.jsx (1)
  • useAuth (5-5)
backend/controllers/project.controller.js (15)
  • project (67-67)
  • project (84-88)
  • project (143-147)
  • project (170-170)
  • project (196-196)
  • project (230-230)
  • project (272-272)
  • project (310-311)
  • project (352-353)
  • project (399-400)
  • project (444-445)
  • project (485-489)
  • project (501-508)
  • project (561-561)
  • projectId (499-499)
frontend/src/config.js (2)
  • API_URL (2-2)
  • API_URL (2-2)
🔇 Additional comments (5)
frontend/vite.config.js (1)

4-9: LGTM!

The Tailwind CSS integration via @tailwindcss/vite is correctly configured for Tailwind v4. The plugin is properly imported and added to the Vite plugins array.

Minor nit: There's an extra blank line at lines 5-6 that could be cleaned up for consistency.

frontend/index.html (1)

1-21: LGTM!

The HTML formatting improvements are appropriate. The commented-out Tailwind CDN script (line 15) makes sense since Tailwind is now properly integrated via the Vite plugin.

frontend/src/pages/Database.jsx (3)

99-115: Confirmation modal integration looks good.

The handleDelete function correctly removes the window.confirm call per PR objectives. Error handling with toast notifications is in place.

Consider adding a loading state to prevent double-clicks during the delete operation, but this is not critical for the current scope.


72-97: LGTM!

The fetchData callback is properly memoized with useCallback, and the effect dependencies are correctly specified.


65-67: LGTM!

The optional catch binding (ES2019+) is used correctly. The error toast provides adequate user feedback.

Comment on lines +303 to +315
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Record"
message="Are you sure you want to delete this record? This action cannot be undone."
onConfirm={() => {
handleDelete(selectedId);
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}
</table>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalid DOM nesting: ConfirmationModal rendered inside <table> element.

The ConfirmationModal is placed inside the <table> element, which is invalid HTML. Only <thead>, <tbody>, <tfoot>, <tr>, <colgroup>, and <caption> are valid children of <table>. This can cause rendering issues and hydration mismatches in React.

Move the modal outside the table structure.

🛠️ Suggested fix
         </tbody>
-        {showModal && (
-          <ConfirmationModal
-            open={showModal}
-            title="Delete Record"
-            message="Are you sure you want to delete this record? This action cannot be undone."
-            onConfirm={() => {
-              handleDelete(selectedId);
-              setShowModal(false);
-            }}
-            onCancel={() => setShowModal(false)}
-          />
-        )}
       </table>
+      {showModal && (
+        <ConfirmationModal
+          open={showModal}
+          title="Delete Record"
+          message="Are you sure you want to delete this record? This action cannot be undone."
+          onConfirm={() => {
+            handleDelete(selectedId);
+            setShowModal(false);
+          }}
+          onCancel={() => setShowModal(false)}
+        />
+      )}
     </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Record"
message="Are you sure you want to delete this record? This action cannot be undone."
onConfirm={() => {
handleDelete(selectedId);
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}
</table>
</table>
{showModal && (
<ConfirmationModal
open={showModal}
title="Delete Record"
message="Are you sure you want to delete this record? This action cannot be undone."
onConfirm={() => {
handleDelete(selectedId);
setShowModal(false);
}}
onCancel={() => setShowModal(false)}
/>
)}
🤖 Prompt for AI Agents
In @frontend/src/pages/Database.jsx around lines 303 - 315, The
ConfirmationModal component is incorrectly rendered as a child of the table
which causes invalid DOM nesting; move the JSX block that renders
ConfirmationModal (the conditional that uses showModal, onConfirm calling
handleDelete(selectedId) and setShowModal(false), and onCancel calling
setShowModal(false)) out of the <table> element and place it as a sibling (e.g.,
immediately after the closing </table>) so the modal is mounted at the top-level
of the component tree rather than inside the table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui: replace window.confirm with a custom Tailwind CSS modal

2 participants